// See the file "COPYING" in the main distribution directory for copyright.

// Methods relating to low-level ZAM instruction manipulation.

#include "zeek/Desc.h"
#include "zeek/Reporter.h"
#include "zeek/script_opt/ScriptOpt.h"
#include "zeek/script_opt/ZAM/Compile.h"

namespace zeek::detail
	{

const ZAMStmt ZAMCompiler::StartingBlock()
	{
	return ZAMStmt(insts1.size());
	}

const ZAMStmt ZAMCompiler::FinishBlock(const ZAMStmt /* start */)
	{
	return ZAMStmt(insts1.size() - 1);
	}

bool ZAMCompiler::NullStmtOK() const
	{
	// They're okay iff they're the entire statement body.
	return insts1.empty();
	}

const ZAMStmt ZAMCompiler::EmptyStmt()
	{
	return ZAMStmt(insts1.size() - 1);
	}

const ZAMStmt ZAMCompiler::LastInst()
	{
	return ZAMStmt(insts1.size() - 1);
	}

const ZAMStmt ZAMCompiler::ErrorStmt()
	{
	return ZAMStmt(0);
	}

OpaqueVals* ZAMCompiler::BuildVals(const ListExprPtr& l)
	{
	return new OpaqueVals(InternalBuildVals(l.get()));
	}

ZInstAux* ZAMCompiler::InternalBuildVals(const ListExpr* l, int stride)
	{
	auto exprs = l->Exprs();
	int n = exprs.length();

	auto aux = new ZInstAux(n * stride);

	int offset = 0; // offset into aux info
	for ( int i = 0; i < n; ++i )
		{
		auto& e = exprs[i];
		int num_vals = InternalAddVal(aux, offset, e);
		ASSERT(num_vals == stride);
		offset += num_vals;
		}

	return aux;
	}

int ZAMCompiler::InternalAddVal(ZInstAux* zi, int i, Expr* e)
	{
	if ( e->Tag() == EXPR_ASSIGN )
		{ // We're building up a table constructor
		auto& indices = e->GetOp1()->AsListExpr()->Exprs();
		auto val = e->GetOp2();
		int width = indices.length();
		int num_vals;

		for ( int j = 0; j < width; ++j )
			{
			num_vals = InternalAddVal(zi, i + j, indices[j]);
			ASSERT(num_vals == 1);
			}

		num_vals = InternalAddVal(zi, i + width, val.get());
		ASSERT(num_vals == 1);

		return width + 1;
		}

	if ( e->Tag() == EXPR_LIST )
		{ // We're building up a set constructor
		auto& indices = e->AsListExpr()->Exprs();
		int width = indices.length();

		for ( int j = 0; j < width; ++j )
			{
			int num_vals = InternalAddVal(zi, i + j, indices[j]);
			ASSERT(num_vals == 1);
			}

		return width;
		}

	if ( e->Tag() == EXPR_FIELD_ASSIGN )
		{
		// These can appear when we're processing the expression
		// list for a record constructor.
		auto fa = e->AsFieldAssignExpr();
		e = fa->GetOp1().get();

		if ( e->GetType()->Tag() == TYPE_TYPE )
			{
			// Ugh - we actually need a "type" constant.
			auto v = e->Eval(nullptr);
			ASSERT(v);
			zi->Add(i, v);
			return 1;
			}

		// Now that we've adjusted, fall through.
		}

	if ( e->Tag() == EXPR_NAME )
		zi->Add(i, FrameSlot(e->AsNameExpr()), e->GetType());

	else
		zi->Add(i, e->AsConstExpr()->ValuePtr());

	return 1;
	}

const ZAMStmt ZAMCompiler::AddInst(const ZInstI& inst, bool suppress_non_local)
	{
	ZInstI* i;

	if ( pending_inst )
		{
		i = pending_inst;
		pending_inst = nullptr;
		}
	else
		i = new ZInstI();

	*i = inst;

	insts1.push_back(i);

	top_main_inst = insts1.size() - 1;

	if ( suppress_non_local )
		return ZAMStmt(top_main_inst);

	// Ensure we haven't confused ourselves about any pending stores.
	ASSERT(pending_global_store == -1 || pending_capture_store == -1);

	if ( pending_global_store >= 0 )
		{
		auto gs = pending_global_store;
		pending_global_store = -1;

		auto store_inst = ZInstI(OP_STORE_GLOBAL_V, gs);
		store_inst.op_type = OP_V_I1;
		store_inst.t = globalsI[gs].id->GetType();

		return AddInst(store_inst);
		}

	if ( pending_capture_store >= 0 )
		{
		auto cs = pending_capture_store;
		pending_capture_store = -1;

		auto& cv = *func->GetType()->AsFuncType()->GetCaptures();
		auto& c_id = cv[cs].Id();

		ZOp op;

		if ( ZVal::IsManagedType(c_id->GetType()) )
			op = OP_STORE_MANAGED_CAPTURE_VV;
		else
			op = OP_STORE_CAPTURE_VV;

		auto store_inst = ZInstI(op, RawSlot(c_id.get()), cs);
		store_inst.op_type = OP_VV_I2;

		return AddInst(store_inst);
		}

	return ZAMStmt(top_main_inst);
	}

const Stmt* ZAMCompiler::LastStmt(const Stmt* s) const
	{
	if ( s->Tag() == STMT_LIST )
		{
		auto sl = s->AsStmtList()->Stmts();
		ASSERT(! sl.empty());
		return sl.back().get();
		}

	else
		return s;
	}

ZAMStmt ZAMCompiler::PrevStmt(const ZAMStmt s)
	{
	return ZAMStmt(s.stmt_num - 1);
	}

	} // zeek::detail
